// 版权所有2009年围棋作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 包打印机实现AST节点的打印。
package printer

import (
	"fmt"
	"go/ast"
	"go/build/constraint"
	"go/token"
	"io"
	"os"
	"strings"
	"text/tabwriter"
	"unicode"
)

const (
	maxNewlines = 2     // 源文本之间的最大换行数
	debug       = false // 启用以进行调试
	infinity    = 1 << 30
)

type whiteSpace byte

const (
	ignore   = whiteSpace(0)
	blank    = whiteSpace(' ')
	vtab     = whiteSpace('\v')
	newline  = whiteSpace('\n')
	formfeed = whiteSpace('\f')
	indent   = whiteSpace('>')
	unindent = whiteSpace('<')
)

// pmode值表示当前打印机模式。
type pmode int

const (
	noExtraBlank     pmode = 1 << iota // 在/*-样式注释后禁用额外空白
	noExtraLinebreak                   // 在/*-style注释后禁用额外的换行符
)

type commentInfo struct {
	cindex         int               // 当前评论索引
	comment        *ast.CommentGroup // =打印机注释[cindex]；还是零
	commentOffset  int               // =printer.posFor（printer.comments[cindex].List[0].Pos（））.Offset；或无限
	commentNewline bool              // 如果注释组包含换行符，则为true
}

type printer struct {
	// 配置（初始化后不更改）
	Config
	fset *token.FileSet

	// 现状
	output       []byte       // 原始打印机结果
	indent       int          // 电流压痕
	level        int          // 级别==0：复合文字外部；级别>0:内部复合文字
	mode         pmode        // 当前打印机模式
	endAlignment bool         // 如果已设置，则立即终止对齐
	impliedSemi  bool         // 如果设置，则换行符表示分号
	lastTok      token.Token  // 打印的最后一个令牌（如果是空白，则为token.likel）
	prevOpen     token.Token  // 上一个非大括号“打开”标记（、[、或token.invalize）
	wsbuf        []whiteSpace // 延迟空白
	goBuild      []int        // 全部的开始索引
	plusBuild    []int        // 全部的开始索引

	// 位置
	// 输出位置与输出结果时的位置不同
	// 格式不同于源格式（在数量上）
	// 空白）。如果存在差异且SourcePos设置为
	// 配置模式，
	// 读卡器的原始源位置。
	pos     token.Position // AST（源）空间中的当前位置
	out     token.Position // 输出空间中的当前位置
	last    token.Position // 调用writeString后的pos值
	linePtr *int           // 如果设置，则在*linePtr中记录下一个令牌的.Line

	// 所有源注释的列表，按出现顺序排列。
	comments        []*ast.CommentGroup // 可能是零
	useNodeComments bool                // 如果未设置，则忽略节点的引线和线条注释

	// 关于p.comments[p.cindex]的信息；由nextComment设置。
	commentInfo

	// 已计算节点大小的缓存。
	nodeSizes map[ast.Node]int

	// 最近计算的线位置的缓存。
	cachedPos  token.Pos
	cachedLine int // 对应于cachedPos的行
}

func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
	p.Config = *cfg
	p.fset = fset
	p.pos = token.Position{Line: 1, Column: 1}
	p.out = token.Position{Line: 1, Column: 1}
	p.wsbuf = make([]whiteSpace, 0, 16) // 空格序列很短
	p.nodeSizes = nodeSizes
	p.cachedPos = -1
}

func (p *printer) internalError(msg ...interface{}) {
	if debug {
		fmt.Print(p.pos.String() + ": ")
		fmt.Println(msg...)
		panic("go/printer")
	}
}

// commentsHaveNewline报告是否有属于
// *ast.CommentGroup包含换行符。因为位置信息
// 可能只是部分正确，我们还必须阅读评论文本。
func (p *printer) commentsHaveNewline(list []*ast.Comment) bool {
	// len（列表）>0
	line := p.lineFor(list[0].Pos())
	for i, c := range list {
		if i > 0 && p.lineFor(list[i].Pos()) != line {
			// 并非所有注释都在同一行上
			return true
		}
		if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) {
			return true
		}
	}
	_ = line
	return false
}

func (p *printer) nextComment() {
	for p.cindex < len(p.comments) {
		c := p.comments[p.cindex]
		p.cindex++
		if list := c.List; len(list) > 0 {
			p.comment = c
			p.commentOffset = p.posFor(list[0].Pos()).Offset
			p.commentNewline = p.commentsHaveNewline(list)
			return
		}
		// 我们不应该到达这里（正确的AST没有空的
		// ast.CommentGroup节点），但请保守，然后重试
	}
	// 没有更多的评论
	p.commentOffset = infinity
}

// commentBefore报告当前注释组是否出现
// 在源代码的下一个位置之前打印它
// 不引入隐式分号。
// None
func (p *printer) commentBefore(next token.Position) bool {
	return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline)
}

// commentSizeBefore返回文件的估计大小
// 在下一个位置之前的同一行上的注释。
// None
func (p *printer) commentSizeBefore(next token.Position) int {
	// 保存/还原当前p.commentInfo（p.nextComment（）对其进行修改）
	defer func(info commentInfo) {
		p.commentInfo = info
	}(p.commentInfo)

	size := 0
	for p.commentBefore(next) {
		for _, c := range p.comment.List {
			size += len(c.Text)
		}
		p.nextComment()
	}
	return size
}

// recordLine记录下一个非空白的输出行号
// 标记在*linePtr中。它用于计算一条直线的准确行号
// 格式化构造，独立于挂起（尚未发出）的空格
// 或评论。
// None
func (p *printer) recordLine(linePtr *int) {
	p.linePtr = linePtr
}

// linesFrom返回当前行之间的输出行数
// 输出行和行参数，忽略任何挂起（尚未
// （b）空白或注释。它用于计算精确的
// 格式化构造的大小（行数）。
// None
func (p *printer) linesFrom(line int) int {
	return p.out.Line - line
}

func (p *printer) posFor(pos token.Pos) token.Position {
	// 未频繁使用以缓存整个令牌。位置
	return p.fset.PositionFor(pos, false /* absolute position */)
}

func (p *printer) lineFor(pos token.Pos) int {
	if pos != p.cachedPos {
		p.cachedPos = pos
		p.cachedLine = p.fset.PositionFor(pos, false /* absolute position */).Line
	}
	return p.cachedLine
}

// writeLineDirective写一个
func (p *printer) writeLineDirective(pos token.Position) {
	if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) {
		p.output = append(p.output, tabwriter.Escape) // 在中保护“\n”
		p.output = append(p.output, fmt.Sprintf("// 第%s行：%d\n“，位置文件名，位置行）
		p.output = append(p.output, tabwriter.Escape)
		// p、 出局必须与结果相符
		p.out.Filename = pos.Filename
		p.out.Line = pos.Line
	}
}

// writeIndent写入缩进。
func (p *printer) writeIndent() {
	// 使用“硬”htabs缩进列
	// tabwriter不能丢弃这些数据
	n := p.Config.Indent + p.indent // 包括底部压痕
	for i := 0; i < n; i++ {
		p.output = append(p.output, '\t')
	}

	// 更新职位
	p.pos.Offset += n
	p.pos.Column += n
	p.out.Column += n
}

// writeByte将ch n次写入p.output并更新p.pos。
// 仅用于写入格式化（空白）字符。
func (p *printer) writeByte(ch byte, n int) {
	if p.endAlignment {
		// 忽略任何对齐控制字符；
		// 在队伍的最后，与
		// 一个formfeed，用于指示
		// 现有列。
		switch ch {
		case '\t', '\v':
			ch = ' '
		case '\n', '\f':
			ch = '\f'
			p.endAlignment = false
		}
	}

	if p.out.Column == 1 {
		// 无需在空白前写入行指令
		p.writeIndent()
	}

	for i := 0; i < n; i++ {
		p.output = append(p.output, ch)
	}

	// 更新职位
	p.pos.Offset += n
	if ch == '\n' || ch == '\f' {
		p.pos.Line += n
		p.out.Line += n
		p.pos.Column = 1
		p.out.Column = 1
		return
	}
	p.pos.Column += n
	p.out.Column += n
}

// writeString将字符串s写入p.output并更新p.pos、p.out、，
// 最后一个。如果设置了isLit，则使用tabwriter.Escape字符对s进行转义
// 以防止制表符编写者解释制表符。
// None
// 注意：writeString仅用于写入Go标记、文字和
// 评论，所有评论都必须按字面意思写。因此，这是正确的
// 始终将isLit设置为true。但是，仅当
// 需要（即，当我们不知道s不包含制表符或换行符时）
// 避免处理额外的转义字符，并减少
// 打印机基准提高了10%。
// None
func (p *printer) writeString(pos token.Position, s string, isLit bool) {
	if p.out.Column == 1 {
		if p.Config.Mode&SourcePos != 0 {
			p.writeLineDirective(pos)
		}
		p.writeIndent()
	}

	if pos.IsValid() {
		// 更新p.pos（如果pos无效，则继续使用现有p.pos）
		// 注意：必须在处理生产线开始后执行此操作，因为
		// 如果存在缩进，writeIndent将更新p.pos，但p.pos
		// 是s的位置。
		p.pos = pos
	}

	if isLit {
		// 保护通过tabwriter的对象
		// 不变。请注意，有效的Go程序不能包含
		// tabwriter.Escape字节，因为它们不出现在合法文件中
		// UTF-8序列。
		p.output = append(p.output, tabwriter.Escape)
	}

	if debug {
		p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // 不要更新p.pos！
	}
	p.output = append(p.output, s...)

	// 更新职位
	nlines := 0
	var li int // 最后换行的索引；如果nlines>0，则有效
	for i := 0; i < len(s); i++ {
		// 原始字符串文字可以包含除后引号（`）以外的任何字符。
		if ch := s[i]; ch == '\n' || ch == '\f' {
			// 断线原因
			nlines++
			li = i
			// 文本中的换行符将打断任何列
			// 格式设置到位；忽略任何进一步的对齐
			// 这条线的尽头。
			p.endAlignment = true
		}
	}
	p.pos.Offset += len(s)
	if nlines > 0 {
		p.pos.Line += nlines
		p.out.Line += nlines
		c := len(s) - li
		p.pos.Column = c
		p.out.Column = c
	} else {
		p.pos.Column += len(s)
		p.out.Column += len(s)
	}

	if isLit {
		p.output = append(p.output, tabwriter.Escape)
	}

	p.last = p.pos
}

// writeCommentPrefix在注释前写入空格。
// 如果有任何悬而未决的空白，它会消耗大量的数据
// 这可能有助于很好地定位评论。
// pos是注释位置，位于项目位置的旁边
// 在所有待处理的注释之后，prev是中的上一个注释
// 一组注释（或nil），tok是下一个标记。
// None
func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, tok token.Token) {
	if len(p.output) == 0 {
		// 注释是要打印的第一项-不要写任何空白
		return
	}

	if pos.IsValid() && pos.Filename != p.last.Filename {
		// 注释在不同的文件中-用换行符分隔
		p.writeByte('\f', maxNewlines)
		return
	}

	if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') {
		// 在与最后一项相同的行上进行注释：
		// 用至少一个分离器分离
		hasSep := false
		if prev == nil {
			// 注释组的第一条注释
			j := 0
			for i, ch := range p.wsbuf {
				switch ch {
				case blank:
					// 在注释前忽略任何空格
					p.wsbuf[i] = ignore
					continue
				case vtab:
					// 尊重现有标签-重要
					// 用于注释结构的正确格式
					hasSep = true
					continue
				case indent:
					// 应用待定缩进
					continue
				}
				j = i
				break
			}
			p.writeWhitespace(j)
		}
		// 确保至少有一个分离器
		if !hasSep {
			sep := byte('\t')
			if pos.Line == next.Line {
				// 下一项与注释位于同一行
				// （必须是/*-样式的注释）：分开
				// 使用空白而不是选项卡
				sep = ' '
			}
			p.writeByte(sep, 1)
		}

	} else {
		// 另一行评论：
		// 用至少一个换行符分隔
		droppedLinebreak := false
		j := 0
		for i, ch := range p.wsbuf {
			switch ch {
			case blank, vtab:
				// 在换行之前忽略任何水平空白
				p.wsbuf[i] = ignore
				continue
			case indent:
				// 应用待定缩进
				continue
			case unindent:
				// 如果这不是最后一个未登录项，请应用它
				// 因为它（可能）属于最后一个
				// 构造（例如，多行表达式列表）
				// 这不是关闭一个街区的一部分
				if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent {
					continue
				}
				// 如果下一个标记不是结束标记}，则应用unindent
				// 如果注释似乎与
				// 代币否则，假设未登录项是
				// 关闭阻塞和停止（此场景显示为
				// 案例标签前的注释，其中包含注释
				// 适用于下一个案例，而不是当前案例）
				if tok != token.RBRACE && pos.Column == next.Column {
					continue
				}
			case newline, formfeed:
				p.wsbuf[i] = ignore
				droppedLinebreak = prev == nil // 仅当组的第一条注释
			}
			j = i
			break
		}
		p.writeWhitespace(j)

		// 确定注释前的换行符数
		n := 0
		if pos.IsValid() && p.last.IsValid() {
			n = pos.Line - p.last.Line
			if n < 0 { // 不应该发生
				n = 0
			}
		}

		// 仅在包范围级别（p.indent==0），
		// 如果之前删除了换行符，请添加额外的换行符：
		// 这将在文档之前保留一个空行
		// 包范围级别的评论（第2570期）
		if p.indent == 0 && droppedLinebreak {
			n++
		}

		// 确保至少有一个换行符
		// 如果上一条注释是行注释
		if n == 0 && prev != nil && prev.Text[1] == '/' {
			n = 1
		}

		if n > 0 {
			// 使用formfeeds在注释前打断列；
			// 这类似于使用FormFeed来分离
			// /*-风格注释的单独行
			p.writeByte('\f', nlimit(n))
		}
	}
}

// 如果s仅包含空白，则返回true
// （打印机上下文中只能显示制表符和空格）。
// None
func isBlank(s string) bool {
	for i := 0; i < len(s); i++ {
		if s[i] > ' ' {
			return false
		}
	}
	return true
}

// commonPrefix返回a和b的公共前缀。
func commonPrefix(a, b string) string {
	i := 0
	for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') {
		i++
	}
	return a[0:i]
}

// trimRight返回删除了尾随空格的s。
func trimRight(s string) string {
	return strings.TrimRightFunc(s, unicode.IsSpace)
}

// stripCommonPrefix从/*-样式的注释行中删除公共前缀（除非没有
// 注释行缩进，除第一行外，所有行都有某种形式的空格前缀）。
// 前缀是使用启发式算法计算的，因此注释
// 使用打印机的打印机重新打印每一行后，内容都会很好地排列
// 当前缩进。
// None
func stripCommonPrefix(lines []string) {
	if len(lines) <= 1 {
		return // 最多一行-无事可做
	}
	// 长度（线）>1

	// 此函数中的启发式函数尝试处理一些
	// /*-风格注释的常见模式：注释，其中
	// 开口/*和闭合*/对齐，且
	// 注释文本的其余部分与对齐并缩进
	// 空白或标签，带有垂直“星线”的箱子
	// 在左侧，以及关闭*/位于
	// 与最后一条注释文本相同的行。

	// 计算除第一个之外的所有公共白色前缀的最大值，
	// 最后，和空行，并将空行替换为空
	// 行（第一行以/*开头，没有前缀）。
	// 如果只有第一行和最后一行不是空的，
	// 例如两行注释，或所有内行都包含的注释
	// 是空白的，考虑前缀计算的最后一行。
	// 否则前缀将为空。
	// None
	// 请注意，第一行和最后一行从不为空（它们为空）
	// 分别包含开头/*和结尾*/和
	// 因此，它们可以被空白行检查忽略。
	prefix := ""
	prefixSet := false
	if len(lines) > 2 {
		for i, line := range lines[1 : len(lines)-1] {
			if isBlank(line) {
				lines[1+i] = "" // 范围以行[1]开始
			} else {
				if !prefixSet {
					prefix = line
					prefixSet = true
				}
				prefix = commonPrefix(prefix, line)
			}

		}
	}
	// 如果我们没有前缀，请考虑最后一行。
	if !prefixSet {
		line := lines[len(lines)-1]
		prefix = commonPrefix(line, line)
	}

	/*
	 * Check for vertical "line of stars" and correct prefix accordingly.
	 */
	lineOfStars := false
	if i := strings.Index(prefix, "*"); i >= 0 {
		// 一排星星出现了。
		if i > 0 && prefix[i-1] == ' ' {
			i-- // 从前缀中删除尾随空格，以便星星保持对齐
		}
		prefix = prefix[0:i]
		lineOfStars = true
	} else {
		// 没有一行星星出现。
		// 确定/*
		// 在注释文本开始之前，假设两个
		// 空格而不是/*除非后面的第一个字符
		// /*是一个选项卡。如果第一个注释行为空，但
		// 对于开口/*，假设最多有3个空格或一个制表符。这
		// 空白可以作为后缀出现在公共前缀中。
		first := lines[0]
		if isBlank(first[2:]) {
			// 第一行没有注释文本：
			// 将前缀最多减少3个空格或一个制表符
			// 如果存在-这将保持注释文本缩进
			// 相对于/*和*/（如果缩进）
			// 首先
			i := len(prefix)
			for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
				i--
			}
			if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
				i--
			}
			prefix = prefix[0:i]
		} else {
			// 第一行的注释文本
			suffix := make([]byte, len(first))
			n := 2 // 开盘后开始/*
			for n < len(first) && first[n] <= ' ' {
				suffix[n] = first[n]
				n++
			}
			if n > 2 && suffix[2] == '\t' {
				// 假设“\t”补偿了/*
				suffix = suffix[2:n]
			} else {
				// 否则假设两个空格
				suffix[0], suffix[1] = ' ', ' '
				suffix = suffix[0:n]
			}
			// 将计算出的公共前缀缩短
			// 后缀，如果它是前缀的后缀。
			prefix = strings.TrimSuffix(prefix, string(suffix))
		}
	}

	// 句柄最后一行：如果它只包含一个结束符*/，请对齐它
	// 使用开口/*，否则将文本与另一个对齐
	// 线
	last := lines[len(lines)-1]
	closing := "*/"
	i := strings.Index(last, closing) // i>=0（关闭始终存在）
	if isBlank(last[0:i]) {
		// 最后一行只包含结束符*/
		if lineOfStars {
			closing = " */" // 添加空白以对齐最终星形
		}
		lines[len(lines)-1] = prefix + closing
	} else {
		// 最后一行包含更多注释文本-假设
		// 它与其他行一样对齐，包括
		// 在前缀计算中
		prefix = commonPrefix(prefix, last)
	}

	// 除去第一行和空行以外的所有行中的公共前缀。
	for i, line := range lines {
		if i > 0 && line != "" {
			lines[i] = line[len(prefix):]
		}
	}
}

func (p *printer) writeComment(comment *ast.Comment) {
	text := comment.Text
	pos := p.posFor(comment.Pos())

	const linePrefix = "// “行”
	if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) {
		// 可能是
		// 暂时挂起缩进以保持行指令有效。
		defer func(indent int) { p.indent = indent }(p.indent)
		p.indent = 0
	}

	// 捷径通例
	if text[1] == '/' {
		if constraint.IsGoBuild(text) {
			p.goBuild = append(p.goBuild, len(p.output))
		} else if constraint.IsPlusBuild(text) {
			p.plusBuild = append(p.plusBuild, len(p.output))
		}
		p.writeString(pos, trimRight(text), true)
		return
	}

	// 对于/*-样式的注释，请逐行打印，并让
	// 写函数注意正确的缩进
	lines := strings.Split(text, "\n")

	// 注释从第一列开始，但仍在继续
	// 缩进。对于幂等结果，添加缩进
	// 所有线条看起来都是缩进的
	// before-这将确保公共前缀计算
	// 与格式化次数无关
	// 适用于（1835年发行）。
	if pos.IsValid() && pos.Column == 1 && p.indent > 0 {
		for i, line := range lines[1:] {
			lines[1+i] = "   " + line
		}
	}

	stripCommonPrefix(lines)

	// 写注释行，用formfeed分隔，
	// 在最后一行之后没有换行符
	for i, line := range lines {
		if i > 0 {
			p.writeByte('\f', 1)
			pos = p.pos
		}
		if len(line) > 0 {
			p.writeString(pos, trimRight(line), true)
		}
	}
}

// writeCommentSuffix在注释后写入换行符（如果指示）
// 并处理任何剩余的缩进信息。如果线路中断
// 如果需要，中断的类型（换行符与formfeed）取决于
// 待处理空格。writeCommentSuffix结果指示
// 写了换行符，或者从空白处删除了formfeed
// 缓冲器
// None
func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) {
	for i, ch := range p.wsbuf {
		switch ch {
		case blank, vtab:
			// 忽略尾随空格
			p.wsbuf[i] = ignore
		case indent, unindent:
			// 不要丢失缩进信息
		case newline, formfeed:
			// 如果我们需要换行符，只保留一个
			// 但请记住，如果我们丢弃了任何FormFeed
			if needsLinebreak {
				needsLinebreak = false
				wroteNewline = true
			} else {
				if ch == formfeed {
					droppedFF = true
				}
				p.wsbuf[i] = ignore
			}
		}
	}
	p.writeWhitespace(len(p.wsbuf))

	// 确保我们有一个断线
	if needsLinebreak {
		p.writeByte('\n', 1)
		wroteNewline = true
	}

	return
}

// containsLinebreak报告空白缓冲区是否包含任何换行符。
func (p *printer) containsLinebreak() bool {
	for _, ch := range p.wsbuf {
		if ch == newline || ch == formfeed {
			return true
		}
	}
	return false
}

// intersperseComments使用在下一个标记之前出现的所有注释
// tok并将其与缓冲空白（即空白）一起打印
// 需要在下一个令牌之前写入）。采用启发式方法进行混合
// 注释和空白。intersperseComments结果指示
// 已写入换行符或是否从空白缓冲区中删除了formfeed。
// None
func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
	var last *ast.Comment
	for p.commentBefore(next) {
		for _, c := range p.comment.List {
			p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok)
			p.writeComment(c)
			last = c
		}
		p.nextComment()
	}

	if last != nil {
		// 如果最后一条注释是/*-样式的注释，则下一项
		// 跟在同一行上，但不是逗号，也不是“结束”
		// 紧跟在其对应的“开始”标记之后的标记，
		// 添加额外的分隔符，除非显式禁用。使用空白
		// 作为分隔符，除非我们有挂起的换行符，否则它们不会
		// 禁用，并且我们在复合文本之外，在这种情况下
		// 我们需要换行符（15137期）。
		// TODO（gri）这已经变得过于复杂了。我们应该能够
		// 跟踪我们是否在表达式或语句中
		// 利用这些信息更直接地做出决定。
		needsLinebreak := false
		if p.mode&noExtraBlank == 0 &&
			last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line &&
			tok != token.COMMA &&
			(tok != token.RPAREN || p.prevOpen == token.LPAREN) &&
			(tok != token.RBRACK || p.prevOpen == token.LBRACK) {
			if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 {
				needsLinebreak = true
			} else {
				p.writeByte(' ', 1)
			}
		}
		// 确保在测试结束后有断线
		// 在EOF之前和结束“}”之前，除非显式禁用。
		if last.Text[1] == '/' ||
			tok == token.EOF ||
			tok == token.RBRACE && p.mode&noExtraLinebreak == 0 {
			needsLinebreak = true
		}
		return p.writeCommentSuffix(needsLinebreak)
	}

	// 没有写任何评论-我们不应该到达这里，因为
	// 在这种情况下，不应调用散布注释
	p.internalError("intersperseComments called without pending comments")
	return
}

// whiteWhitespace写入前n个空格条目。
func (p *printer) writeWhitespace(n int) {
	// 写条目
	for i := 0; i < n; i++ {
		switch ch := p.wsbuf[i]; ch {
		case ignore:
			// 忽视
		case indent:
			p.indent++
		case unindent:
			p.indent--
			if p.indent < 0 {
				p.internalError("negative indentation:", p.indent)
				p.indent = 0
			}
		case newline, formfeed:
			// 紧接着“纠正”的换行符
			// 未登录与未登录进行交换-这允许
			// 正确的标签定位。如果评论介于
			// 换行符和标签，不包含
			// 注释空白前缀和注释的一部分
			// 将正确定位并缩进。
			if i+1 < n && p.wsbuf[i+1] == unindent {
				// 使用formfeed终止当前节。
				// 否则，下一行的长标签名称将导致
				// 到宽列可能会增加列的缩进
				// 标签前的行数；有效地导致错误
				// 缩进。
				p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
				i-- // 再做一次
				continue
			}
			fallthrough
		default:
			p.writeByte(byte(ch), 1)
		}
	}

	// 向下移动剩余条目
	l := copy(p.wsbuf, p.wsbuf[n:])
	p.wsbuf = p.wsbuf[:l]
}

// ----------------------------------------------------------------------------
// 打印接口

// nlimit将n限制为最大换行。
func nlimit(n int) int {
	if n > maxNewlines {
		n = maxNewlines
	}
	return n
}

func mayCombine(prev token.Token, next byte) (b bool) {
	switch prev {
	case token.INT:
		b = next == '.' // 1.
	case token.ADD:
		b = next == '+' // ++
	case token.SUB:
		b = next == '-' // --
	case token.QUO:
		b = next == '*' // /*
	case token.LSS:
		b = next == '-' || next == '<' // <-或<<
	case token.AND:
		b = next == '&' || next == '^' // &&或&^
	}
	return
}

// print打印“项”列表（大致对应于语法
// 标记，但也包括空格和格式信息）。
// 它是唯一可以直接从中调用的打印函数
// nodes.go中的任何AST打印函数。
// None
// 空白将累积，直到出现非空白标记。任何
// 首先打印标记之前需要出现的注释，
// 考虑到任何待决白名单的数量和结构-
// 最佳评论位置的空间。然后，任何剩余的空白都是
// 已打印，后跟实际令牌。
// None
func (p *printer) print(args ...interface{}) {
	for _, arg := range args {
		// 有关当前参数的信息
		var data string
		var isLit bool
		var impliedSemi bool // 此参数后p.impliedSemi的值

		// 记录以前的期初令牌（如果有）
		switch p.lastTok {
		case token.ILLEGAL:
			// 忽略（空白）
		case token.LPAREN, token.LBRACK:
			p.prevOpen = p.lastTok
		default:
			// 其他代币紧跟在任何开始代币之后
			p.prevOpen = token.ILLEGAL
		}

		switch x := arg.(type) {
		case pmode:
			// 切换打印机模式
			p.mode ^= x
			continue

		case whiteSpace:
			if x == ignore {
				// 不要将忽略添加到缓冲区；他们
				// 可能会“纠正”不符合项（参见
				// 标签（MT）
				continue
			}
			i := len(p.wsbuf)
			if i == cap(p.wsbuf) {
				// 空格序列非常短，因此应该
				// 永远不会发生。优雅地处理（但可能是
				// 如果真的发生了，则返回错误的注释位置。
				p.writeWhitespace(i)
				i = 0
			}
			p.wsbuf = p.wsbuf[0 : i+1]
			p.wsbuf[i] = x
			if x == newline || x == formfeed {
				// 换行符影响当前状态（p.impliedSemi）
				// 而不是打印参数后的状态（impliedSemi）
				// 因为注释可以散布在arg之前
				// 在这种情况下
				p.impliedSemi = false
			}
			p.lastTok = token.ILLEGAL
			continue

		case *ast.Ident:
			data = x.Name
			impliedSemi = true
			p.lastTok = token.IDENT

		case *ast.BasicLit:
			data = x.Value
			isLit = true
			impliedSemi = true
			p.lastTok = x.Kind

		case token.Token:
			s := x.String()
			if mayCombine(p.lastTok, s[0]) {
				// 上一个令牌和当前令牌必须是
				// 用空格隔开，否则它们会合并
				// 进入另一个不正确的令牌序列
				// （除了token.INT后跟“.”之外
				// 不应该发生，因为它被小心了
				// （通过二进制表达式格式化）
				if len(p.wsbuf) != 0 {
					p.internalError("whitespace buffer not empty")
				}
				p.wsbuf = p.wsbuf[0:1]
				p.wsbuf[0] = ' '
			}
			data = s
			// 某些关键字后跟换行符表示分号
			switch x {
			case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
				token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
				impliedSemi = true
			}
			p.lastTok = x

		case token.Pos:
			if x.IsValid() {
				p.pos = p.posFor(x) // 下一项的准确位置
			}
			continue

		case string:
			// 不正确的AST-打印错误消息
			data = x
			isLit = true
			impliedSemi = true
			p.lastTok = token.STRING

		default:
			fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg)
			panic("go/printer type")
		}
		// 数据！=""

		next := p.pos // 下一项的估计/准确位置
		wroteNewline, droppedFF := p.flush(next, p.lastTok)

		// 如果源代码中存在额外的换行符，则在源代码和
		// 如果它们没有引起额外的分号（不要在
		// 刷新，因为它会导致文件末尾出现额外的换行符）
		if !p.impliedSemi {
			n := nlimit(next.Line - p.pos.Line)
			// 如果我们已经写了一行，就不要超过maxNewlines
			if wroteNewline && n == maxNewlines {
				n = maxNewlines - 1
			}
			if n > 0 {
				ch := byte('\n')
				if droppedFF {
					ch = '\f' // 使用formfeed，因为我们以前丢弃过一个
				}
				p.writeByte(ch, n)
				impliedSemi = false
			}
		}

		// 下一个令牌现在开始-如果请求，记录其行号
		if p.linePtr != nil {
			*p.linePtr = p.out.Line
			p.linePtr = nil
		}

		p.writeString(next, data, isLit)
		p.impliedSemi = impliedSemi
	}
}

// flush打印文本形式出现的任何待定注释和空白
// 在下一个令牌tok的位置之前。刷新结果表明
// 如果写了换行符或从空白处删除了formfeed
// 缓冲器
// None
func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
	if p.commentBefore(next) {
		// 如果在下一项之前有注释，则将其散布
		wroteNewline, droppedFF = p.intersperseComments(next, tok)
	} else {
		// 否则，请写出剩余的空格
		p.writeWhitespace(len(p.wsbuf))
	}
	return
}

// getNode返回与n关联的ast.CommentGroup（如果有）。
func getDoc(n ast.Node) *ast.CommentGroup {
	switch n := n.(type) {
	case *ast.Field:
		return n.Doc
	case *ast.ImportSpec:
		return n.Doc
	case *ast.ValueSpec:
		return n.Doc
	case *ast.TypeSpec:
		return n.Doc
	case *ast.GenDecl:
		return n.Doc
	case *ast.FuncDecl:
		return n.Doc
	case *ast.File:
		return n.Doc
	}
	return nil
}

func getLastComment(n ast.Node) *ast.CommentGroup {
	switch n := n.(type) {
	case *ast.Field:
		return n.Comment
	case *ast.ImportSpec:
		return n.Comment
	case *ast.ValueSpec:
		return n.Comment
	case *ast.TypeSpec:
		return n.Comment
	case *ast.GenDecl:
		if len(n.Specs) > 0 {
			return getLastComment(n.Specs[len(n.Specs)-1])
		}
	case *ast.File:
		if len(n.Comments) > 0 {
			return n.Comments[len(n.Comments)-1]
		}
	}
	return nil
}

func (p *printer) printNode(node interface{}) error {
	// 解包*CommentedNode（如果有）
	var comments []*ast.CommentGroup
	if cnode, ok := node.(*CommentedNode); ok {
		node = cnode.Node
		comments = cnode.Comments
	}

	if comments != nil {
		// 注释节点-将注释列表限制在相关范围内
		n, ok := node.(ast.Node)
		if !ok {
			goto unsupported
		}
		beg := n.Pos()
		end := n.End()
		// 如果节点具有相关文档，
		// 将该评论组包括在范围内
		// （注释列表按顺序排序
		// 注释的外观（在源代码中）
		if doc := getDoc(n); doc != nil {
			beg = doc.Pos()
		}
		if com := getLastComment(n); com != nil {
			if e := com.End(); e > end {
				end = e
			}
		}
		// 标记。Pos值是全局偏移量，我们可以
		// 直接比较它们
		i := 0
		for i < len(comments) && comments[i].End() < beg {
			i++
		}
		j := i
		for j < len(comments) && comments[j].Pos() < end {
			j++
		}
		if i < j {
			p.comments = comments[i:j]
		}
	} else if n, ok := node.(*ast.File); ok {
		// 使用ast.File注释（如果有）
		p.comments = n.Comments
	}

	// 如果没有注释，请使用节点注释
	p.useNodeComments = p.comments == nil

	// 准备好评论供使用
	p.nextComment()

	p.print(pmode(0))

	// 格式节点
	switch n := node.(type) {
	case ast.Expr:
		p.expr(n)
	case ast.Stmt:
		// 标记语句将取消缩进以定位标签。
		// 将p.indent设置为1，这样我们就不会得到缩进“下溢”。
		if _, ok := n.(*ast.LabeledStmt); ok {
			p.indent = 1
		}
		p.stmt(n, false)
	case ast.Decl:
		p.decl(n)
	case ast.Spec:
		p.spec(n, 1, false)
	case []ast.Stmt:
		// 标记语句将取消缩进以定位标签。
		// 将p.indent设置为1，这样我们就不会得到缩进“下溢”。
		for _, s := range n {
			if _, ok := s.(*ast.LabeledStmt); ok {
				p.indent = 1
			}
		}
		p.stmtList(n, 0, false)
	case []ast.Decl:
		p.declList(n)
	case *ast.File:
		p.file(n)
	default:
		goto unsupported
	}

	return nil

unsupported:
	return fmt.Errorf("go/printer: unsupported node type %T", node)
}

// ----------------------------------------------------------------------------
// 修剪机

// 微调器是一个io.Writer过滤器，用于剥离tabwriter.Escape
// 字符、尾随空格和制表符，以及用于转换formfeed
// 将vtab字符转换为换行符和htab（如果没有tabwriter
// （已使用）。由tabwriter括起的文本。传递转义字符
// 通过保持不变。
// None
type trimmer struct {
	output io.Writer
	state  int
	space  []byte
}

// trimmer作为状态机实现。
// 它可以处于以下状态之一：
const (
	inSpace  = iota // 内部空间
	inEscape        // 由tabwriter.Escapes括起的内部文本
	inText          // 内部文本
)

func (p *trimmer) resetSpace() {
	p.state = inSpace
	p.space = p.space[0:0]
}

// 设计说明：在设计中消除多余的空白是很有诱惑力的
// 此函数中的空格，因为它可以简化一些
// 节点打印功能中的空白逻辑。
// 但是，这会弄乱用户所做的任何格式设置
// 作者。

var aNewline = []byte("\n")

func (p *trimmer) Write(data []byte) (n int, err error) {
	// 不变量：
	// p、 state==inSpace:
	// p、 空间是不成文的
	// p、 state==inEscape，inText:
	// 数据[m:n]是未写入的
	m := 0
	var b byte
	for n, b = range data {
		if b == '\v' {
			b = '\t' // 转换为htab
		}
		switch p.state {
		case inSpace:
			switch b {
			case '\t', ' ':
				p.space = append(p.space, b)
			case '\n', '\f':
				p.resetSpace() // 丢弃尾随空间
				_, err = p.output.Write(aNewline)
			case tabwriter.Escape:
				_, err = p.output.Write(p.space)
				p.state = inEscape
				m = n + 1 // +1：跳过tabwriter。退出
			default:
				_, err = p.output.Write(p.space)
				p.state = inText
				m = n
			}
		case inEscape:
			if b == tabwriter.Escape {
				_, err = p.output.Write(data[m:n])
				p.resetSpace()
			}
		case inText:
			switch b {
			case '\t', ' ':
				_, err = p.output.Write(data[m:n])
				p.resetSpace()
				p.space = append(p.space, b)
			case '\n', '\f':
				_, err = p.output.Write(data[m:n])
				p.resetSpace()
				if err == nil {
					_, err = p.output.Write(aNewline)
				}
			case tabwriter.Escape:
				_, err = p.output.Write(data[m:n])
				p.state = inEscape
				m = n + 1 // +1：跳过tabwriter。退出
			}
		default:
			panic("unreachable")
		}
		if err != nil {
			return
		}
	}
	n = len(data)

	switch p.state {
	case inEscape, inText:
		_, err = p.output.Write(data[m:n])
		p.resetSpace()
	}

	return
}

// ----------------------------------------------------------------------------
// 公共接口

// 模式值是一组标志（或0）。他们控制印刷。
type Mode uint

const (
	RawFormat Mode = 1 << iota // 不要使用制表符；如果设置，将忽略UseSpace
	TabIndent                  // 使用与UseSpace无关的缩进选项卡
	UseSpaces                  // 使用空格而不是制表符进行对齐
	SourcePos                  // 发出
)

// 打印机的公共API中不包括以下模式，因为
// 编辑代码文本被视为超出范围。因为这个模式是
// 未报告的情况下，还可以根据
// go/format和cmd/gofmt不断发展的需求
// 用户。见CL 240683中的讨论。
const (
	// normalizeEnumbers意味着规范化数字
	// 打印时使用文字前缀和指数。
	// None
	// 该值在go/format和cmd/gofmt中已知并由其使用。
	// 目前，它更方便，性能更好
	// 在打印期间应用数字标准化的包，
	// 而不是预先修改AST。
	normalizeNumbers Mode = 1 << 30
)

// 配置节点控制Fprint的输出。
type Config struct {
	Mode     Mode // 默认值：0
	Tabwidth int  // 默认值：8
	Indent   int  // 默认值：0（所有代码至少缩进了这么多）
}

// fprint实现fprint并获取一个节点设置打印机状态的映射。
func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) {
	// 打印节点
	var p printer
	p.init(cfg, fset, nodeSizes)
	if err = p.printNode(node); err != nil {
		return
	}
	// 打印未完成的评论
	p.impliedSemi = false // EOF就像一条新线
	p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)

	// 输出现在缓冲在p.output中。
	// 修理
	p.fixGoBuildLines()

	// 通过微调器重定向输出以消除尾随空白
	// （由于尾随选项卡提供
	// 格式化信息。制表符可以提供修剪
	// 设置RawFormat时，不使用tabwriter功能。）
	output = &trimmer{output: output}

	// 如有必要，通过tabwriter重定向输出
	if cfg.Mode&RawFormat == 0 {
		minwidth := cfg.Tabwidth

		padchar := byte('\t')
		if cfg.Mode&UseSpaces != 0 {
			padchar = ' '
		}

		twmode := tabwriter.DiscardEmptyColumns
		if cfg.Mode&TabIndent != 0 {
			minwidth = 0
			twmode |= tabwriter.TabIndent
		}

		output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
	}

	// 通过tabwriter/trimmer将打印机结果写入输出
	if _, err = output.Write(p.output); err != nil {
		return
	}

	// 冲洗制表符（如有）
	if tw, _ := output.(*tabwriter.Writer); tw != nil {
		err = tw.Flush()
	}

	return
}

// CommentedNode绑定AST节点和相应的注释。
// 它可以作为任何Fprint函数的参数提供。
// None
type CommentedNode struct {
	Node     interface{} // *文件或ast.Expr、ast.Decl、ast.Spec或ast.Stmt
	Comments []*ast.CommentGroup
}

// Fprint“pretty打印”一个AST节点以输出给定配置。
// 位置信息相对于文件集fset进行解释。
// 节点类型必须为*ast.File、*CommentedNode、[]ast.Decl、[]ast.Stmt、，
// 或与ast.Expr、ast.Decl、ast.Spec或ast.Stmt兼容的赋值。
// None
func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
	return cfg.fprint(output, fset, node, make(map[ast.Node]int))
}

// Fprint“pretty打印”要输出的AST节点。
// 它使用默认设置调用Config.Fprint。
// 请注意，gofmt使用制表符进行缩进，但使用空格进行对齐；
// 对与gofmt匹配的输出使用format.Node（包go/format）。
// None
func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
	return (&Config{Tabwidth: 8}).Fprint(output, fset, node)
}
